package hu.mapro.roller.common;

import org.jbox2d.collision.shapes.ChainShape;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.MathUtils;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.joints.WheelJointDef;

public class RollerCommon {

	// View
	
	public static final float VIEW_WIDTH = 10; 

	// Ground

	private static final float GROUND_BOTTOM = -100;

	private static final float GROUND_END = 50;
	private static final float GROUND_START = -GROUND_END;
	private static final float GROUND_AMPLITUDE = 15;
	private static final float GROUND_LEVEL = 0;
	
	private static final int GROUND_FRACTAL_LEVELS = 2;
	private static final float GROUND_FRACTAL_DENOMINATOR = 2.0f;
	
	float[] terrainVertices;
	int terrainVerticeIdx = 0;

	// Car
	
	private static final float CAR_WIDTH = 3.0f;
	private static final float CAR_PLATFORM_HEIGHT = 0.5f;
	private static final float CAR_WALL_HEIGHT = 0.5f;
	private static final float CAR_WALL_WIDTH = 0.5f;
	private static final float CAR_WHEEL_RADIUS = 1.0f;
	private static final float CAR_WHEEL_DISTANCE = 2.5f;
	private static final float CAR_WHEEL_SUSPENSION = -0.75f;
	
	private static final float CARGO_SIZE = 0.75f;

	public Body carBody;
	private Body wheel1Body;
	private Body wheel2Body;

	private float m_hz;
	private float m_zeta;
//	private float m_speed;
//	private WheelJoint m_spring1;
//	private WheelJoint m_spring2;	
	
	// Physics

	public World world;
	
	static final float BOX_STEP=1/60f;
	static final int  BOX_VELOCITY_ITERATIONS=6;
	static final int BOX_POSITION_ITERATIONS=3;	

	float accumulator;
	
//	private Box2DDebugRenderer debugRenderer;
	

	private static final Vec2 defaultGravity = new Vec2(0, -10);

	public Body groundBody;

	public Fixture carPlatformFixture;
	
	public float getMiddlePointX() {
		return terrainVertices[terrainVertices.length/2-1];
	}
	
	public float getMiddlePointHeight() {
		return terrainVertices[terrainVertices.length/2];
	}
	
	void generateFractal() {
		createVertexArray((1 << GROUND_FRACTAL_LEVELS) + 1 + 2);
		
		
		float y1 = (float)(GROUND_LEVEL + (2*Math.random()*GROUND_AMPLITUDE) - GROUND_AMPLITUDE);
		float y2 = (float)(GROUND_LEVEL + (2*Math.random()*GROUND_AMPLITUDE) - GROUND_AMPLITUDE);
		fractal(
				GROUND_START,
				y1,
				GROUND_END,
				y2,
				GROUND_AMPLITUDE,
				GROUND_FRACTAL_LEVELS
		);
		
		addVertex(GROUND_END, y2);
		addVertex(GROUND_END, GROUND_BOTTOM);
		addVertex(GROUND_START, GROUND_BOTTOM);
	}
	
	void createVertexArray(int size) {
		terrainVertices = new float[size*2];
	}
	
	void addVertex(float x, float y) {
		terrainVertices[terrainVerticeIdx++] = x;
		terrainVertices[terrainVerticeIdx++] = y;
	}

	void fractal(
			float x1,
			float y1,
			float x2, 
			float y2, 
			float d,
			int n
	) {
		if (n==0) {
			addVertex(x1, y1);
		} else {
			float xm = (x1+x2)/2;
			float ym = (float) ((y1+y2)/2 + (Math.random()*d*2)-d);
			fractal(x1, y1, xm, ym, d/GROUND_FRACTAL_DENOMINATOR, n-1);
			fractal(xm, ym, x2, y2, d/GROUND_FRACTAL_DENOMINATOR, n-1);
		}
	}


	public void step (float delta) {
		accumulator+=delta;
	    while(accumulator>BOX_STEP){
	      world.step(BOX_STEP,BOX_VELOCITY_ITERATIONS,BOX_POSITION_ITERATIONS);
	      accumulator-=BOX_STEP;
	   }
	}
	
	public void init () {
		generateFractal();
		
		// physics
		
		world = new World(defaultGravity);
		world.setAllowSleep(false);
		
		// - car
		
	    m_hz = 4.0f;
	    m_zeta = 0.7f;
	    //m_speed = 50.0f;
		
		PolygonShape carPlatformShape = new PolygonShape();
		Vec2 carPlatformVertices[] = new Vec2[] {
				new Vec2(-CAR_WIDTH/2.0f, -(CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f),
				new Vec2(CAR_WIDTH/2.0f, -(CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f),
				new Vec2(CAR_WIDTH/2.0f, (CAR_PLATFORM_HEIGHT-CAR_WALL_HEIGHT)/2.0f),
				new Vec2(-CAR_WIDTH/2.0f, (CAR_PLATFORM_HEIGHT-CAR_WALL_HEIGHT)/2.0f),
		};
		carPlatformShape.set(carPlatformVertices, carPlatformVertices.length);

		PolygonShape carLeftWallShape = new PolygonShape();
		Vec2 carLeftWallVertices[] = new Vec2[] {
				new Vec2(-CAR_WIDTH/2.0f, (CAR_PLATFORM_HEIGHT-CAR_WALL_HEIGHT)/2.0f),
				new Vec2(-CAR_WIDTH/2.0f, (CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f),
				new Vec2(-CAR_WIDTH/2.0f+CAR_WALL_WIDTH, (CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f),
				new Vec2(-CAR_WIDTH/2.0f+CAR_WALL_WIDTH, (CAR_PLATFORM_HEIGHT-CAR_WALL_HEIGHT)/2.0f),
		};
		carLeftWallShape.set(carLeftWallVertices, carLeftWallVertices.length);
		
		PolygonShape carRightWallShape = new PolygonShape();
		Vec2 carRightWallVertices[] = new Vec2[] {
				new Vec2(CAR_WIDTH/2.0f, (CAR_PLATFORM_HEIGHT-CAR_WALL_HEIGHT)/2.0f),
				new Vec2(CAR_WIDTH/2.0f, (CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f),
				new Vec2(CAR_WIDTH/2.0f-CAR_WALL_WIDTH, (CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f),
				new Vec2(CAR_WIDTH/2.0f-CAR_WALL_WIDTH, (CAR_PLATFORM_HEIGHT-CAR_WALL_HEIGHT)/2.0f),
		};
		carRightWallShape.set(carRightWallVertices, carRightWallVertices.length);
		
		CircleShape circle = new CircleShape();
		circle.setRadius(CAR_WHEEL_RADIUS);

		
		float carX = getMiddlePointX();
		float carY = getMiddlePointHeight()+CAR_WHEEL_RADIUS*1.2f-CAR_WHEEL_SUSPENSION+(CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f;
		
		BodyDef bd = new BodyDef();
		bd.type = BodyType.DYNAMIC;
		bd.position.set(carX, carY);
		carBody = world.createBody(bd);
		
		carPlatformFixture = carBody.createFixture(carPlatformShape, 1.0f);
		carBody.createFixture(carLeftWallShape, 1.0f);
		carBody.createFixture(carRightWallShape, 1.0f);

		
		// - cargo
		
		PolygonShape cargoShape = new PolygonShape();
		Vec2 cargoVertices[] = new Vec2[] {
				new Vec2(-CARGO_SIZE/2.0f, -CARGO_SIZE/2.0f),
				new Vec2(CARGO_SIZE/2.0f, -CARGO_SIZE/2.0f),
				new Vec2(CARGO_SIZE/2.0f, CARGO_SIZE/2.0f),
				new Vec2(-CARGO_SIZE/2.0f, CARGO_SIZE/2.0f),
		};
		cargoShape.set(cargoVertices, cargoVertices.length);
		bd.position.set(carX, carY);
		Body cargoBody = world.createBody(bd);
		cargoBody.createFixture(cargoShape, 1.0f);
		
		
		FixtureDef fd = new FixtureDef();
		fd.shape = circle;
		fd.density = 1.0f;
		fd.friction = 0.9f;

		bd.position.set(carX-CAR_WHEEL_DISTANCE/2.0f, carY-(CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f+CAR_WHEEL_SUSPENSION);
		wheel1Body = world.createBody(bd);
		wheel1Body.createFixture(fd);

		bd.position.set(carX+CAR_WHEEL_DISTANCE/2.0f, carY-(CAR_PLATFORM_HEIGHT+CAR_WALL_HEIGHT)/2.0f+CAR_WHEEL_SUSPENSION);
		wheel2Body = world.createBody(bd);
		wheel2Body.createFixture(fd);

		WheelJointDef jd = new WheelJointDef();
		Vec2 axis = new Vec2(0.0f, 1.0f);

		jd.initialize(carBody, wheel1Body, wheel1Body.getPosition(), axis);
		jd.motorSpeed = 0.0f;
		jd.enableMotor = false;
		jd.frequencyHz = m_hz;
		jd.dampingRatio = m_zeta;
		world.createJoint(jd);

		jd.initialize(carBody, wheel2Body, wheel2Body.getPosition(), axis);
		jd.motorSpeed = 0.0f;
		jd.enableMotor = false;
		jd.frequencyHz = m_hz;
		jd.dampingRatio = m_zeta;
		world.createJoint(jd);		
		
		// - ground
		BodyDef groundBodyDef =new BodyDef();  
		groundBody = world.createBody(groundBodyDef);
		
		ChainShape groundShape = new ChainShape();
		Vec2[] vecs = new Vec2[terrainVertices.length/2];
		for (int i=0;i<vecs.length;i++) {
			vecs[i] = new Vec2(terrainVertices[i*2], terrainVertices[i*2+1]);
		}
		groundShape.createChain(vecs, vecs.length);
		
		groundBody.createFixture(groundShape, 0.0f);
	
	}
	
	public Vec2 rotate(Vec2 v, float degrees) {
		return rotateLocal(new Vec2(v), degrees); 
	}
	
	public Vec2 rotateLocal (Vec2 v, float degrees) {
		float rad = degrees * MathUtils.DEG2RAD;
		float cos = (float)Math.cos(rad);
		float sin = (float)Math.sin(rad);

		float newX = v.x * cos - v.y * sin;
		float newY = v.x * sin + v.y * cos;

		v.x = newX;
		v.y = newY;

		return v;
	}

	public void setGravityAngle(float degrees) {
		world.setGravity(rotate(defaultGravity, degrees));
	}
	
}
